﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using Networking;

namespace SlaveComponent
{

    public enum SlaveState { IDLE, HANDSHAKING, WORKING };

    public class Slave
    {
        /// <summary>
        /// La porta su cui il master è in ascolto.
        /// </summary>
        public static int MASTER_PORT = 80;

        private SlaveState _Status;
        private NetworkStream _Network;
        private Version _Version;
        private SecurityType _SecurityAdopted;

        /// <summary>
        /// Lo stato attuale dello slave.
        /// </summary>
        public SlaveState Status
        {
            set { _Status = value; }
            get { return _Status; }
        }

        /// <summary>
        /// Crea un nuovo oggetto slave associandolo al network stream specificato.
        /// La versione del protocollo IVC è impostata di default a "000.001".
        /// </summary>
        /// <param name="Network">il NetworkStream da associare allo slave.</param>
        public Slave(NetworkStream Network)
        {
            InternalInit(Network, new Version("000.001"));
        }

        /// <summary>
        /// Crea un nuovo oggetto slave associandolo al network stream specificato.
        /// La versione del protocollo IVC è impostata come indicato.
        /// </summary>
        /// <param name="Network">il NetworkStream da associare allo slave.</param>
        /// <param name="Version">la versione del protocollo IVC.</param>
        public Slave(NetworkStream Network, Version Version)
        {
            InternalInit(Network, Version);            
        }

        private void InternalInit(NetworkStream Network, Version Version)
        {
            _Version = Version;
            _Network = Network;

            _Status = SlaveState.IDLE;
        }

        /// <summary>
        /// Invia al master le informazioni sul protocollo IVC supportato.
        /// </summary>
        public void ConfigProtocolVersion()
        {
            // send protocol version packet
            /*
            ProtocolVersionPacket prVrsPkt = new ProtocolVersionPacket(_Version.ToString());
            byte[] bufPrVrs = prVrsPkt.Serialize();
            _Network.Write(bufPrVrs, 0, bufPrVrs.Length);
            */

            // Modified by HeDo to comply with new packetfactory class

            byte[] buffer = PacketFactory.CreatePacket(PacketType.ProtocolVersion, _Version.ToString()).Serialize();

            _Network.Write(buffer, 0, buffer.Length);

            // read ack response
            /*
            byte[] buffer = new byte[2];
            _Network.Read(buffer, 0, 2);
            AckNackPacket ackPkt = new AckNackPacket(buffer);

            if (!ackPkt.IsOK)
                throw new ProtocolAgreementFailedException(_Version);
            */

            buffer = new byte[2];
            _Network.Read(buffer, 0, 2);

            AckNackPacket ack = (AckNackPacket)PacketFactory.ParsePacket(PacketType.AckNack, buffer);

            if (!ack.IsOK) throw new ProtocolAgreementFailedException(_Version);

        }

        /// <summary>
        /// Dialoga con il master impostando le configurazione relative alla sicurezza.
        /// </summary>
        public void ConfigSecurity()
        {
            /*
            // send security packet, only "None" supported at this moment
            SecurityType[] valid = new SecurityType[1];
            valid[0] = SecurityType.None;
            SecurityPacket styPkt = new SecurityPacket(valid);
            */

            // Modified by HeDo to comply with new packetfactory class

            byte[] bufSty = PacketFactory.CreatePacket(PacketType.Security, SecurityType.None).Serialize();

            _Network.Write(bufSty, 0, bufSty.Length);

            // read security response
            byte[] buffer = new byte[1];
            _Network.Read(buffer, 0, 1);

            SecurityResponsePacket styResp = (SecurityResponsePacket) PacketFactory.ParsePacket(PacketType.SecurityResponse, buffer);

            if (styResp.AcceptedSecurity == SecurityType.None) 
                _SecurityAdopted = SecurityType.None;
            else 
                throw new SecurityTypeAgreementFailedException();
            /*
            if (styPkt.IsSupported(styResp.AcceptedSecurity))
                _SecurityAdopted = styResp.AcceptedSecurity;
            else
                throw new SecurityTypeAgreementFailedException();
            */
            // send ack response

            byte[] bufAck = PacketFactory.CreatePacket(PacketType.AckNack, true).Serialize();

            _Network.Write(bufAck, 0, bufAck.Length);

        }

        public void TestSecurity()
        {
            /*byte[] buffer = new byte[100];
            _Network.Read(buffer, 0, buffer.Length);
            AutenticationPacket authPkt = new AutenticationPacket();
            if(buffer.
            
            //System.Windows.Forms.MessageBox(buffer.ToString(),"Password ricevuta:");
            //La password viene reinviata in chiaro, ma essendo il sistema di crypt Plain
            //verranno inviati gli stessi dati

            _Network.Write();
             */
            
        }

        /// <summary>
        /// Chiude la connessione con il master.
        /// </summary>
        public void CloseConnection()
        {
            _Network.Close();
        }

        /// <summary>
        /// Restituisce un socket connesso all'indirizzo ip e alla porta indicati.
        /// </summary>
        /// <param name="ipAddress">la stringa contenente l'indirizzo ip a cui connettersi.</param>
        /// <param name="port">la porta a cui connettersi.</param>
        /// <returns>il socket se la connessione è stata effettuata.</returns>
        /// <exception cref="SocketException">la connessione non è andata a buon fine.</exception>
        public static Socket ConnectSocket(string ipAdd)
        {
            // creazione del socket
            IPAddress ipAddress = IPAddress.Parse(ipAdd);
            IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, Slave.MASTER_PORT);
            Socket tempSocket = new Socket(ipEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

            // connessione del socket
            tempSocket.Connect(ipEndPoint);

            return tempSocket;
        }
    }
}
